package IC.Types;

import IC.AST.ICClass;

import java.util.LinkedHashMap;
import java.util.Map;

public class TypeTable {

	private Map<Type, ArrayType> arrayTypesMap;
	private Map<String, ClassType> classTypesMap;
	private Map<String, MethodType> methodTypesMap;
	private PrimitiveType stringType;
	private PrimitiveType voidType;
	private PrimitiveType booleanType;
	private PrimitiveType integerType;
	private PrimitiveType nullType;
	private String icFilePath;

	public TypeTable(String filePath) {
		icFilePath = filePath;

		InitTypeMaps();
		InitTypes();
	}

	public PrimitiveType getVoidPrimitiveType() {
		return voidType;
	}

	public PrimitiveType getNullPrimitiveType() {
		return nullType;
	}

	public PrimitiveType getBoolPrimitiveType() {
		return booleanType;
	}

	public PrimitiveType getIntegerPrimitiveType() {
		return integerType;
	}

	public PrimitiveType getStringPrimitiveType() {
		return stringType;
	}

	public ClassType ClassNameToClassType(String className) {
		return classTypesMap.containsKey(className) ? classTypesMap.get(className) : null;
	}

	public ClassType ClassToClassType(ICClass icClass) { 
		ClassType classType = null;
		if (classTypesMap.containsKey(icClass.getName())) {
			classType = classTypesMap.get(icClass.getName());
			if (classType.getIcClass() != icClass) {
				// there are two classes with the same name
				return null;
			}
		} else {
			ClassType containingClassType = null;
			if (icClass.parentClassExists()) {
				if ((containingClassType = ClassNameToClassType(icClass.getParentClassName())) != null)
					icClass.setParentSemanticType(containingClassType);
				else
					return containingClassType;
			}
			classType = new ClassType(icClass, containingClassType);
			classTypesMap.put(icClass.getName(), classType);
			icClass.setSemanticType(classType);
		}
		return classType;
	}

	public ArrayType getArrayType(Type type) {
		ArrayType arrayType = null;
		if (arrayTypesMap.containsKey(type)) {
			arrayType = arrayTypesMap.get(type);
		}
		else{
			arrayType = new ArrayType(type);
			arrayTypesMap.put(type, arrayType);
		}
		return arrayType;
	}

	public Type getArrayType(Type type, int length) {
		Type currentArrayType = type;
		int i;
		for (i = 0; i < length; i++)
			currentArrayType = getArrayType(currentArrayType);

		return currentArrayType;
	}

	public MethodType getMethodType(Type[] parametersTypeArray, Type returnType) {
		StringBuilder stringBuilder = new StringBuilder("{");
		for (Type type : parametersTypeArray)
			stringBuilder.append(", "+type.getTypeName());
		String methodName = stringBuilder.delete(1, 3).append(" -> "+returnType.getTypeName()+"}").toString();

		if (!methodTypesMap.containsKey(methodName)) {
			MethodType methodType = new MethodType(parametersTypeArray, returnType, methodName);
			methodTypesMap.put(methodName, methodType);
			return methodType;
		}
		return methodTypesMap.get(methodName);
	}

	private void InitTypes() {
		voidType = new PrimitiveType(PrimitiveTypeKind.VOID);
		nullType = new PrimitiveType(PrimitiveTypeKind.NULL);
		booleanType = new PrimitiveType(PrimitiveTypeKind.BOOLEAN);
		integerType = new PrimitiveType(PrimitiveTypeKind.INT);
		stringType = new PrimitiveType(PrimitiveTypeKind.STRING);
	}

	private void InitTypeMaps() {
		classTypesMap = new LinkedHashMap<String, ClassType>();	
		arrayTypesMap = new LinkedHashMap<Type, ArrayType>();
		methodTypesMap = new LinkedHashMap<String, MethodType>();
	}

	public String toString() {
		StringBuilder stringBuilder = new StringBuilder("Type Table: ");

		stringBuilder.append(icFilePath + "\n");

		stringBuilder.append(voidType.toString() + "\n" + nullType.toString() + "\n" + booleanType.toString() + "\n" + 
				integerType.toString()+"\n" +stringType.toString()+"\n");

		for(ClassType classType : classTypesMap.values())
			stringBuilder.append(classType.toString()+'\n');

		for(ArrayType arrayType : arrayTypesMap.values())
			stringBuilder.append(arrayType.toString()+'\n');

		for(MethodType methodType : methodTypesMap.values())
			stringBuilder.append(methodType.toString()+'\n');

		return stringBuilder.toString();
	}
}